/*
 * $Id: sysapi_trimpath.c,v 1.11 2005-12-26 07:41:48 bacon Exp $
 */

#include <xp/bas/sysapi.h>
#include <xp/bas/memory.h>
#include <xp/bas/string.h>
#include <xp/bas/assert.h>
#include <xp/bas/errno.h>

#ifdef _WIN32
	#define IS_SEPARATOR(c) ((c) == XP_CHAR('/') || (c) == XP_CHAR('\\'))
	#define IS_DRIVELETTER(c) \
		(((c) >= XP_CHAR('A') && c <= XP_CHAR('Z')) || \
		 ((c) >= XP_CHAR('a') && c <= XP_CHAR('z')))
#else
	#define IS_SEPARATOR(c) ((c) == XP_CHAR('/'))
#endif

xp_size_t xp_trimpath (const xp_char_t* src, xp_char_t* dst, xp_size_t dstcnt)
{
#ifdef __VMS
	/* TODO: add VMS support */
	xp_seterrno (XP_ENOTSUP);
	return (xp_size_t)-1;
#else

	const xp_char_t* s = src;
	xp_char_t* d, * buf, * org_buf = XP_NULL;
	xp_size_t n;

	n = xp_strsize(src);

	if (dstcnt >= n) {
		/* if the destination is large enough to hold the source
		 * without trimming, it can be used aas a temporary working
		 * storage because the trimmed result will never exceed
		 * its size */
		buf = dst;
	}
	else {
		/* explicit memory allocation is required as larger temporary
		 * spoace than the size of the destination might be needed.
		 *   /a/b/c/d/e/f/g/h ### /../../../../../../../../ ### */
		buf = (xp_char_t*)xp_malloc (n * xp_sizeof(xp_char_t)); 
		if (buf == XP_NULL) {
			xp_seterrno (XP_ENOMEM);
			return (xp_size_t)-1;
		}
		org_buf = buf;
	}

#ifdef _WIN32
	if (IS_DRIVELETTER(*s) && s[1] == XP_CHAR(':')) {
		/* secure spaces for the drive name */

		if (buf != dst && dstcnt <= 2) {
			xp_assert (org_buf != XP_NULL);
			xp_free (org_buf);
			xp_seterrno (XP_ENOBUFS);
			return (xp_size_t)-1;
		}

		*buf++ = *s; *dst++ = *s; s++;
		*buf++ = *s; *dst++ = *s; s++;
		src += 2; dstcnt -= 2;
	}
	else if (IS_SEPARATOR(*s) && s[1] == *s /*&& s[2] != *s*/) {
		/* secure spaces for network drive */

		if (buf != dst && dstcnt <= 2) {
			xp_assert (org_buf != XP_NULL);
			xp_free (org_buf);
			xp_seterrno (XP_ENOBUFS);
			return (xp_size_t)-1;
		}

		*buf++ = *s; *dst++ = *s; s++;
		*buf++ = *s; *dst++ = *s; s++;
		src += 2; dstcnt -= 2;

		/* TODO: more checks for valid network drive name */
		while (!IS_SEPARATOR(*s) && *s != XP_CHAR('\0')) {
			*buf++ = *s; *dst++ = *s; s++; src++;
			
			if (buf != dst && dstcnt <= 1) {
				xp_assert (org_buf != XP_NULL);
				xp_free (org_buf);
				xp_seterrno (XP_ENOBUFS);
				return (xp_size_t)-1;
			}

			dstcnt--;
		}
	}
#endif

	d = buf;

	if (IS_SEPARATOR(*s)) {
		/* copy an initial slash */
		/* *d++ = *s++; */
		*d++ = *s; /* doesn't advance to the next postion of src */
	}
	else if (*s == XP_CHAR('.') && IS_SEPARATOR(s[1])) {
		/* skips initial . of ./ */
		s++;
	}

	while (*s != XP_CHAR('\0')) {
		if (!IS_SEPARATOR(*s)) {
			*d++ = *s++;
			continue;
		}

		/* removes consecutive slashes */
		while (IS_SEPARATOR(s[1])) s++;

		if (s[1] == XP_CHAR('.') && IS_SEPARATOR(s[2])) {
			/* skip /. */
			s += 2;
		}
		else if (s[1] == XP_CHAR('.') && s[2] == XP_CHAR('\0')) {
			/* end with /. */
			break;
		}
		else if (s[1] == XP_CHAR('.') && s[2] == XP_CHAR('.') && 
		         (IS_SEPARATOR(s[3]) || s[3] == XP_CHAR('\0'))) {
			xp_bool_t append = xp_false;

			/* check if popping the previous directory is needed */
			if (d - buf >= 3) {
				if (d[-1] == XP_CHAR('.') && 
				    d[-2] == XP_CHAR('.') && 
				    IS_SEPARATOR(d[-3])) {
					append = xp_true;
				}
			}
			else if (d - buf == 2) {
				if (d[-1] == XP_CHAR('.') && 
				    d[-2] == XP_CHAR('.')) {
					append = xp_true;
				}
			}
			else if (d - buf == 0) append = xp_true;

			if (append) {
				if (d == buf) s++;
				else *d++ = *s++; /* / */
				*d++ = *s++; /* . */
				*d++ = *s++; /* . */
			}
			else {
				/* pop the previous directory */
				while (d > buf) {
					d--;
					if (IS_SEPARATOR(*d)) {
						if (d == buf) d++;
						break;
					}
				}
				s += 3;
			}
		}
		else if (s[1] == XP_CHAR('\0')) {
			/* ends with a slash. remove the ending slash */
			break;
		}
		else {
			if (d == buf) s++;
			else if (IS_SEPARATOR(d[-1])) s++;
			else *d++ = *s++;
		}
	}

	/* if the path is empty after trimming, 
	 * reset it to the current directory */
	if (d == buf && s != src) *d++ = XP_CHAR('.');

	if (buf == dst) {
		*d = XP_CHAR('\0');	
		return d - buf;
	}
	else {
		xp_assert (org_buf != XP_NULL);

		n = d - buf;
		if (n >= dstcnt) {
			xp_free (org_buf);
			xp_seterrno (XP_ENOBUFS);
			return (xp_size_t)-1;
		}

		xp_strncpy (dst, buf, n);
		xp_free (org_buf);
		return n;
	}
#endif
}

